#!/bin/bash
#
# Implements a simple filesystem-based context storage backend, which uses a
# tree of directories and files to store context data.
#
# Author:   Dr. Mike Murphy (mmurphy2@coastal.edu)
# Revision: 12 November 2013
#
#   Copyright 2012-2013 Coastal Carolina University
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#


## p_context_backend_filesystem_get <path>
##
## Retrieves context information located at <path> from the filesystem. If
## <path> refers to a directory, retrieves and cat's the data from
## <path>/parent.data. If <path> does not exist in the filesystem, does not
## produce output.
##
## Returns 0 normally, 2 if the context path is invalid, or another non-zero
## status if a filesystem error occurs when trying to read the data.
##
function p_context_backend_filesystem_get() {
	local item
	local status=0
	local cdpath=$(echo "${CONTEXT_BACKEND}" | sed s/filesystem://)
	
	if p_context_validate_path "$1"; then
		item="${cdpath}/$1"
		if [[ -d "${item}" ]]; then
			item="${item}/parent.data"
		fi
			
		if [[ -f "${item}" ]]; then
			cat "${item}"
			status=$?
		fi
	else
		status=2
		echo "Invalid context name: $1"
	fi
	return ${status}
}


## p_context_backend_filesystem_set <path> <value>
##
## Sets the context item at <path> to <value>, provided that the current
## value of the item at <path> is different from the new <value> (or the
## <path> does not yet exist)*. Otherwise, the context item is unchanged, in
## order to minimize filesystem writes for solid-state devices.
##
## If the requested <path> is a directory, sets the context value to
## <path>/parent.data, which is an invalid context path within Pulley.
## Similarly, if it would be necessary to create a directory where a flat
## file already exists, the context of the flat file are moved into the
## newly created directory inside a parent.data file. The backend function
## p_context_backend_filesystem_get reverses this process to retrieve the
## data later.
##
## * When checking to see if existing context matches the newly requested
## value, this function calls p_context_backend_filesystem_get directly. This
## approach keeps the filesystem data internally consistent if, for some
## reason, multiple context storage backends are used at once.
##
## Returns 0 if the operation succeeds, 2 if <path> is invalid, or other
## non-zero value if a filesystem error occurs.
## 
function p_context_backend_filesystem_set() {
	local status=0
	local cdpath=$(echo "${CONTEXT_BACKEND}" | sed s/filesystem://)
	local dirpath=$(dirname "$1")
	local itemname=$(basename "$1")
	local tempdata
	
	if p_context_validate_path "$1"; then
		# First check to see if the data to be written is really different than
		# existing context. This way, we minimize writes, in case the back-end
		# storage device is a solid-state device.
		tempdata=$(p_context_backend_filesystem_get "$1")
		if [[ "${tempdata}" != "$2" ]]; then
			
			# Fix up the directory path to place it inside the proper location
			if [[ "${dirpath}" == "." ]]; then
				dirpath="${cdpath}"
			else
				dirpath="${cdpath}/${dirpath}"
			fi
			
			# If there is a file at the requested directory path, move the
			# contents into the special "parent.data" file. Otherwise, if the
			# directory path does not yet exist, create it.
			if [[ -f "${dirpath}" ]]; then
				tempdata=$(cat dirpath)
				rm -f "${dirpath}"
				mkdir -p "${dirpath}"
				status=$?
				echo "${tempdata}" > "${dirpath}/parent.data"
			elif [[ ! -d "${dirpath}" ]]; then
				mkdir -p "${dirpath}"
				status=$?
			fi
			
			# If we are trying to write a context item that already has child
			# items, must write to the parent.data file inside the directory.
			if [[ -d "${dirpath}/${itemname}" ]]; then
				itemname="${itemname}/parent.data"
			fi
		
			# If there have been no errors, write the data
			if [[ ${status} -eq 0 ]]; then
				echo "$2" > "${dirpath}/${itemname}"
				status=$?
			fi
		fi
	else
		status=2
		echo "Invalid context name: $1"
	fi
	
	return ${status}
}


## p_context_backend_filesystem_clear <path>
##
## Clears the context item stored at <path> on the filesystem.
##
## Returns 0 if the operation succeeds, 2 if <path> is invalid, or other
## non-zero value if a filesystem error occurs.
##
function p_context_backend_filesystem_clear() {
	# Just set the file to an empty value, since this has the same effect.
	p_context_filesystem_set "$1" ""
	return $?
}
